// Generic table layout implementation
// Copyright 2005 (C) Ralph Thomas

#include <view/table.h>

namespace view {
	struct coordinate {
		long	x;	///< Dimension in x-axis.
		long	y;	///< Dimension in y-axis.
		enum	selection	{ xaxis, yaxis };

		coordinate() : x( 0 ), y( 0 ) {}
		long&	get( selection sel ) {
			if( sel == xaxis ) return x;
			return y;
		}
	};
	//
	/// The table class contains the layout information and handles
	/// child positioning.
	//
	class table {
		coordinate	offset;		///< Offset for next child.
		coordinate	maximum;	///< Maximum dimensions of child.
		coordinate	position;	///< Position of widget.
		coordinate	dimensions;	///< Dimensions of widget.
		long getLength( coordinate::selection axis, const adobe::eve_t::place_data_t& pdata )
		{
			if( axis == coordinate::xaxis )
				return pdata.horizontal().length_m;
			return pdata.vertical().length_m;
		}
	  public:
		table() {}
		//
		/// This function is called to place children. It knows to
		/// wrap them on to the next line if the line gets too long.
		///
		/// \param	placer	index to the actual placer function.
		/// \param	cdata	calculation data.
		/// \param	pdata	placement data.
		//
		void move( coordinate::selection axis, const adobe::eve_t::place_data_t& pdata,
			long& x, long& y ) {
			coordinate child;
			coordinate::selection otheraxis = coordinate::xaxis;
			if( axis == coordinate::xaxis )
				otheraxis = coordinate::yaxis;
			//
			// If _xoff is zero then put it on this row.
			//
			child.get( otheraxis ) += offset.get( otheraxis );
			if( offset.get( axis ) ) {
				//
				// If _xoff + child width is less than our own width
				// then it can go on this row.
				//
				int off = offset.get( axis ) + getLength( axis, pdata );
				if( off <= dimensions.get( axis ) ) {
					child.get( axis ) += offset.get( axis );
				} else {
					//
					// Start a new row.
					//
					child.get( otheraxis ) += maximum.get( otheraxis );
					offset.get( otheraxis ) += maximum.get( otheraxis );
					maximum.get( otheraxis ) = 0;
					maximum.get( axis ) = 0;
					offset.get( axis ) = 0;
				}
			}
			//
			// If this widget's height is more than anything else
			// so far on this row then it becomes the row maximum.
			//
			if( getLength( otheraxis, pdata ) > maximum.get( otheraxis ) )
				maximum.get( otheraxis ) = getLength( otheraxis, pdata );
			//
			// Move along by the child width, so that the next child
			// isn't placed on top.
			//
			offset.get( axis ) += getLength( axis, pdata );
			x = child.x; y = child.y;
		}
		void place( const adobe::eve_t::calculate_data_t& cdata,
			const adobe::eve_t::place_data_t& pdata ) {
			//
			// We only need information from the placement data.
			//
			position.get( coordinate::xaxis ) = pdata.horizontal().position_m;
			position.get( coordinate::yaxis ) = pdata.vertical().position_m;
			dimensions.get( coordinate::xaxis ) = pdata.horizontal().length_m;
			dimensions.get( coordinate::yaxis ) = pdata.horizontal().length_m;
			//
			// Reset our positioning.
			//
			offset.get( coordinate::xaxis ) = 0;
			offset.get( coordinate::yaxis ) = 0;
			maximum.get( coordinate::xaxis ) = 0;
			maximum.get( coordinate::yaxis ) = 0;
		}
		void reset( long w, long h ) {
			offset.get( coordinate::xaxis ) = 0;
			offset.get( coordinate::yaxis ) = 0;
			dimensions.get( coordinate::xaxis ) = w;
			dimensions.get( coordinate::yaxis ) = h;
		}
		adobe::extents_t best_bounds() {
			adobe::extents_t bounds;
			bounds.width()	= 10;
			bounds.height()	= 10;
			return bounds;
		}
	};
	adobe::widget_node_t table_factory(
		const adobe::dictionary_t&	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token ) {
		//
		// Determine if we are laying out with row priority or column
		// priority.
		//
		adobe::name_t placement = adobe::static_name_t( "place_row" );
		parameters.get( adobe::static_name_t( "placement" ), placement );
		coordinate::selection selection = coordinate::xaxis;
		if( placement == adobe::static_name_t( "place_column" ) )
			selection = coordinate::yaxis;
		//
		// Build a set of parameters with the placement set to overlay.
		//
		adobe::dictionary_t my_params( parameters );
		my_params.set( adobe::static_name_t( "placement" ),
			adobe::static_name_t( "place_overlay" ) );
		if( !my_params.count( adobe::static_name_t( "horizontal" ) ) )
			my_params.set( adobe::static_name_t( "horizontal" ),
				adobe::static_name_t( "align_fill" ) );
		if( !my_params.count( adobe::static_name_t( "vertical" ) ) )
			my_params.set( adobe::static_name_t( "vertical" ),
				adobe::static_name_t( "align_fill" ) );
		//
		// Create a new table (which Eve sees as an overlay container)
		// and do the things required to push it into Eve.
		//
		table* t = new table;
		token.assemblage_m.delete_on_destruction( t );
		//
		// Set up the signals so that we get notified for child placement.
		//
		adobe::eve_t::signal_suite_t	signals;

		signals.eve_calculate_proc_m = boost::bind(
			&eve_client::utility_widget_calculate,
			t->best_bounds(), _1 );
		signals.eve_place_proc_m = boost::bind(
			&table::place, t, _1, _2 );
		signals.eve_custom_layout_m = boost::bind(
			&table::reset, t, _1, _2 );
		signals.eve_custom_layout_move_m = boost::bind(
			&table::move, t, selection, _1, _2, _3 );
		//
		// Tell Eve that we're an overlay container (we're not really).
		//
		adobe::eve_t::calculate_data_t cdata;
		cdata.placement_m = adobe::eve_t::place_overlay;
		//
		// Insert our table widget into Eve so that we get told about
		// when children are added.
		//
		adobe::eve_t::iterator eve_token(
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					cdata,
					true,
					my_params,
					signals )
				)
			);
		//
		// Return the token for children.
		//
		return adobe::widget_node_t( parent.size_m, eve_token,
			parent.display_token_m, parent.overlay_token_m );
	}
};
